<!--
@license
Copyright 2016 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<link rel="import" href="../paper-button/paper-button.html">
<link rel="import" href="../paper-radio-group/paper-radio-group.html">
<link rel="import" href="../paper-dialog/paper-dialog.html">
<link rel="import" href="../paper-tooltip/paper-tooltip.html">
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../tf-backend/tf-backend.html">
<link rel="import" href="../tf-dashboard-common/dashboard-style.html">
<link rel="import" href="../tf-dashboard-common/tf-dashboard-layout.html">
<link rel="import" href="../tf-tensorboard/registry.html">
<link rel="import" href="tf-beholder-video.html">
<link rel="import" href="tf-beholder-info.html">

<dom-module id="tf-beholder-dashboard">
  <template>
    <tf-dashboard-layout>
      <div class="sidebar">
        <template is="dom-if" if="[[_controls_disabled]]">
          <div class="sidebar-section">
            <p class="controls-disabled-message">
              Controls disabled: directory is not writeable.
            </p>
            <p class="disclaimer">
              Beholder requires write access to the log directory in order
              to communicate visualization changes to the <code>Beholder</code>
              instance in your model.
            </p>
          </div>
        </template>
        <div class="sidebar-section">
          <div class="title">Values</div>
          <paper-radio-group
            id="valuesSelector"
            selected="{{_values}}">
            <paper-radio-button name="trainable_variables"
                                disabled="[[_controls_disabled]]">
              <pre>tf.trainable_variables()</pre>
            </paper-radio-button>
            <paper-radio-button id="option-arrays" name="arrays"
                                disabled="[[_controls_disabled]]">
              <pre>b.update(arrays=[NP_ARRAYS])</pre>
            </paper-radio-button>
            <paper-radio-button id="option-frames" name="frames"
                                disabled="[[_controls_disabled]]">
              <pre>b.update(frame=NP_ARRAY)</pre>
            </paper-radio-button>
          </paper-radio-group>

          <template is="dom-if" if="[[_valuesNotFrame(_values)]]">
            <paper-checkbox
              checked="{{_showAll}}"
              disabled="[[_controls_disabled]]"
            >Show all data <i>(can be resource intensive)</i></paper-checkbox>
          </template>
        </div>

        <template is="dom-if" if="[[_valuesNotFrame(_values)]]">

          <div class="sidebar-section">
            <div class="title">Mode</div>
            <paper-radio-group id="modeSelector" selected="{{_mode}}">
              <paper-radio-button name="current"
                                  disabled="[[_controls_disabled]]">
                current values
              </paper-radio-button>
              <paper-radio-button name="variance"
                                  disabled="[[_controls_disabled]]">
                variance over train steps
              </paper-radio-button>
            </paper-radio-group>
            <template is="dom-if" if="[[_varianceSelected(_mode)]]">
              <div class="title">Variance timesteps: {{_windowSize}}</div>
              <paper-slider
                id="windowSlider"
                value="{{_windowSize}}"
                type="number"
                step="1"
                min="2"
                max="20"
                pin="true"
                disabled="[[_controls_disabled]]">
              </paper-slider>
            </template>
          </div>

          <div class="sidebar-section">
            <div class="title">Image scaling</div>
            <paper-radio-group id="scalingSelector" selected="{{_scaling}}">

              <paper-radio-button id="option-layer" name="layer"
                                  disabled="[[_controls_disabled]]">
                per section
              </paper-radio-button>
              <paper-tooltip for="option-layer" position="right">
                Black is the lowest value in that section, white is that largest value in that section.
              </paper-tooltip>

              <paper-radio-button id="option-network" name="network"
                                  disabled="[[_controls_disabled]]">
                all sections
              </paper-radio-button>
              <paper-tooltip for="option-network" position="right">
                Black is the smallest value in all sections, white is the largest value in all sections.
              </paper-tooltip>
            </paper-radio-group>

            <div id="colormap-selection">
              <div id="colormap-selection-label">Colormap:</div>
              <paper-dropdown-menu
                no-label-float
                selected-item-label="{{_colormap}}"
                disabled="[[_controls_disabled]]"
              >
                <paper-menu class="dropdown-content" selected="0">
                  <paper-item>magma</paper-item>
                  <paper-item>inferno</paper-item>
                  <paper-item>plasma</paper-item>
                  <paper-item>viridis</paper-item>
                  <paper-item>grayscale</paper-item>
                </paper-menu>
              </paper-dropdown-menu>
            </div>
          </div>

        </template>

        <div class="sidebar-section">
          <div class="title">Updates per second: {{_FPS}}</div>
          <paper-slider
            id="FPSSlider"
            value="{{_FPS}}"
            type="number"
            step="1"
            min="0"
            max="30"
            pin="true"
            disabled="[[_controls_disabled]]">
          </paper-slider>
        </div>

        <div class="sidebar-section">
          <paper-button
            class="x-button"
            id="record_button"
            on-tap="_toggleRecord"
            disabled="[[_controls_disabled]]">
            [[_recordText]]
          </paper-button>
        </div>

        <div class="sidebar-section">
          <p class="disclaimer">Note: Beholder currently only works well on local file systems.</p>
        </div>

      </div>
      <div class="center">

        <template is="dom-if" if="[[!_is_active]]">
          <div class="no-data-warning">
            <h3>No Beholder data was found.</h3>

            <p>Probable causes:
            <ul>
              <li>Your script isn't running.</li>
              <li>You aren't calling <code>beholder.update()</code>.</li>
            </ul>

            <p>To use Beholder, import and instantiate the <code>Beholder</code>
            class, and call its <code>update</code> method with a
            <code>Session</code> argument after every train step:</p>

            <pre>
from tensorboard.plugins.beholder import Beholder
beholder = Beholder(LOG_DIRECTORY)

# inside train loop
beholder.update(
  session=sess,
  arrays=list_of_np_arrays,  # optional argument
  frame=two_dimensional_np_array,  # optional argument
)</pre>
            <p>If using <code>tf.train.MonitoredSession</code>, you can use
            <code>BeholderHook</code>:

            <pre>
from tensorboard.plugins.beholder import BeholderHook
beholder_hook = BeholderHook(LOG_DIRECTORY)
with MonitoredSession(..., hooks=[beholder_hook]) as sess:
  sess.run(train_op)</pre>

            <p>If you think everything is set up properly, please see
            <a href="https://github.com/tensorflow/tensorboard/blob/master/tensorboard/plugins/beholder/README.md">the README</a>
            for more information and consider filing an issue on GitHub.

            <p class="disclaimer">Note: Beholder currently only works well on local file systems.</p>
          </div>
        </template>

        <template is="dom-if" if="[[_is_active]]">
          <tf-beholder-video id="video" fps="[[_FPS]]"></tf-beholder-video>

          <template is="dom-if" if="[[_valuesNotFrame(_values)]]">
            <tf-beholder-info
              id="info"
              fps="[[_FPS]]">
            </tf-beholder-info>
          </template>
        </template>

      </div>
    </tf-dashboard-layout>
    <style include="dashboard-style"></style>
    <style>
      .center {
        height: 100%;
        display: flex;
        padding: 0;
      }

      .no-data-warning {
        max-width: 540px;
        margin: 80px auto 0 auto;
      }

      .title {
        font-size: 16px;
        margin: 8px 5px 8px 0;
        color: black;
      }

      .title small {
        font-weight: normal;
      }

      paper-checkbox {
        display: block;
        padding: 4px;
      }

      paper-radio-button {
        display: block;
        padding: 5px;
      }

      paper-radio-group {
        margin-top: 5px;
      }

      paper-slider {
        margin-left: 12px;
        --paper-slider-knob-color: var(--tb-orange-strong);
        --paper-slider-active-color: var(--tb-orange-strong);
        flex-grow: 2;
      }

      pre {
        display: inline;
      }

      paper-button#record_button {
        color: #D32F2F;
      }

      paper-button#record_button.is-recording {
        background: #D32F2F;
        color: white;
      }

      .sidebar-section.beholder-dashboard:last-child {
        flex-grow: 0
      }

      #colormap-selection {
        display: flex;
        margin-top: 5px;
      }

      #colormap-selection-label {
        margin-top: 13px;
      }

      #colormap-selection paper-dropdown-menu {
        margin-left: 10px;
        --paper-input-container-focus-color: var(--tb-orange-strong);
        width: 105px;
      }

      .sidebar {
        max-width: 350px;
      }

      p.disclaimer {
        color: #999;
        font-style: italic;
      }

      p.controls-disabled-message {
        color: #C00;
        font-weight: bold;
      }

    </style>
  </template>
  <script>
    "use strict";
    (function() {

    const PLUGIN_NAME = 'beholder'

    Polymer({
      is: 'tf-beholder-dashboard',

      properties: {

        _requestManager: {
          type: Object,
          value: () => new tf_backend.RequestManager(10, 0),
        },

        ////

        _values: {
          type: String,
          value: 'trainable_variables',
          observer: '_configChanged',
        },

        _mode: {
          type: String,
          value: 'variance',
          observer: '_configChanged',
        },

        _scaling: {
          type: String,
          value: 'layer',
          observer: '_configChanged',
        },

        _windowSize: {
          type: Number,
          value: 15,
          observer: '_configChanged',
        },

        _previousFPS: {
          type: Number,
          value: 30,
        },

        _FPS: {
          type: Number,
          value: 10,
          observer: '_configChanged',
        },

        _recordText: {
          type: String,
          value: 'start recording'
        },

        _isRecording: {
          type: Boolean,
          value: false,
          observer: '_configChanged',
        },

        _showAll: {
          type: Boolean,
          value: false,
          observer: '_configChanged'
        },

        _colormap: {
          type: String,
          value: 'magma',
          observer: '_configChanged'
        },

        _is_active: {
          type: Boolean,
          value: false,
          observer: '_configChanged',
        },

        _controls_disabled: {
          type: Boolean,
          value: false,
          observer: '_configChanged',
        },
      },

      _valuesNotFrame(values) {
        return values !== 'frames'
      },

      _varianceSelected(mode) {
        return mode === 'variance';
      },

      _configChanged() {
        // Skip recording config unless we're active and controls are enabled.
        if (!this._is_active || this._controls_disabled) {
          return;
        }

        // In case we aren't finished initializing yet.
        const properties = [
          this._values,
          this._mode,
          this._scaling,
          this._windowSize,
          this._FPS,
          this._isRecording,
          this._showAll,
          this._colormap
        ]

        for (var property of properties) {
          if (typeof property === 'undefined' || property === '') {
            return
          }
        }

        const url = tf_backend.getRouter().pluginRoute(PLUGIN_NAME, '/change-config');
        const postData = {
          values: this._values,
          mode: this._mode,
          scaling: this._scaling,
          window_size: this._windowSize,
          FPS: this._FPS,
          is_recording: this._isRecording,
          show_all: this._showAll,
          colormap: this._colormap
        }

        this._requestManager.request(url, postData)
      },

      _toggleRecord() {
        if (this._recordText == 'start recording') {
          this.set('_recordText', 'stop recording')
          this.set('_isRecording', true)
        } else {
          this.set('_recordText', 'start recording')
          this.set('_isRecording', false)
        }

        this.$.record_button.classList.toggle('is-recording')
      },

      ready() {
        this.reload()
      },

      reload() {
        const url = tf_backend.getRouter().pluginRoute(PLUGIN_NAME, '/is-active');

        this._requestManager.request(url).then(response => {
          this.set('_is_active', response['is_active']);
          this.set('_controls_disabled', !response['is_config_writable']);
        })
      },
    });
    })();

    tf_tensorboard.registerDashboard({
      plugin: 'beholder',
      elementName: 'tf-beholder-dashboard',
      shouldRemoveDom: true,
    });
  </script>
</dom-module>
